using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Rogue
{
    public class LabelHelper
    {
        public static LabelHelper Instance = null;

        protected class Label
        {
            public string   m_Text;
            public Entity   m_Entity;
            public Point    MapCoordinates;
            public Color    BackgroundColor;
            public Color    TextColor;
            public Color    BorderColor;
            public float    m_TimeLeft;
            public bool     m_UseTimeLeft;

            public Label(string a_sText, Point a_pCoord)
            {
                m_Text = a_sText;
                MapCoordinates = a_pCoord;
                BackgroundColor = new Color(30, 30, 30);
                TextColor = Color.Wheat;
                BorderColor = Color.SlateGray;
            }

            public Label(string a_sText, Entity a_Follow)
                : this(a_sText, a_Follow.Position)
            {
                m_Entity = a_Follow;
                m_UseTimeLeft = false;
            }

            public Label(string a_sText, Entity a_Follow, float a_TimeUntilRemove)
                : this(a_sText, a_Follow.Position)
            {
                m_Entity = a_Follow;
                m_UseTimeLeft = true;
                m_TimeLeft = a_TimeUntilRemove;
            }
        }
        static int m_nLabelID = 0;
        protected SpriteBatch m_SpriteBatch;
        protected Texture2D m_Texture;
        protected LineHelper m_LineManager;
        protected GameMap m_GameMap;
        protected LineHelper m_LineHelper;
        protected Dictionary<int, Label> m_Labels;
        protected Rectangle[] m_aCellDefs;
        protected Point m_pCellScreenSize;
        protected Point m_pLabelOffsetFromCell = new Point(8, -8);

        public LabelHelper(GraphicsDevice a_Dev, Texture2D a_Texture, GameMap a_GameMap)
        {
            Instance = this;

            m_GameMap = a_GameMap;
            m_Texture = a_Texture;
            m_LineHelper = new LineHelper(a_Dev);
            m_SpriteBatch = new SpriteBatch(a_Dev);
            m_Labels = new Dictionary<int, Label>();
            m_LineManager = new LineHelper(a_Dev);
            BuildCellDefs();

            ConsoleWindow.Instance.RegisterFunction("LABEL.ADD", CONSOLE_AddLabel);
            ConsoleWindow.Instance.RegisterFunction("LABEL.REMOVE", CONSOLE_RemoveLabel);
            ConsoleWindow.Instance.RegisterFunction("LABEL.REMOVEALL", CONSOLE_ClearLabels);
            ConsoleWindow.Instance.RegisterFunction("LABEL.MOVE", CONSOLE_MoveLabel);
        }

        protected Rectangle GetLabelScreenBounds(Label a_Label, Point a_Start)
        {
            Point pMapCellTopRight = a_Start; //  GetMapCellTopRight(a_Label);

            Point pLabelBottomLeft = new Point(
                pMapCellTopRight.X + m_pLabelOffsetFromCell.X, 
                pMapCellTopRight.Y + m_pLabelOffsetFromCell.Y
                );

            return new Rectangle(
                pLabelBottomLeft.X, 
                pLabelBottomLeft.Y - m_pCellScreenSize.Y,
                (a_Label.m_Text.Length + 1)* 8,
                m_pCellScreenSize.Y
                );
        }

        private void DrawBorder(Label a_Label, Point a_Start)
        {
            Rectangle rBorder = GetLabelScreenBounds(a_Label, a_Start);
            m_LineManager.AddLine(new Point(rBorder.Left, rBorder.Top), new Point(rBorder.Right, rBorder.Top), a_Label.BorderColor);
            m_LineManager.AddLine(new Point(rBorder.Right, rBorder.Top), new Point(rBorder.Right, rBorder.Bottom), a_Label.BorderColor);
            m_LineManager.AddLine(new Point(rBorder.Right, rBorder.Bottom), new Point(rBorder.Left, rBorder.Bottom), a_Label.BorderColor);
            m_LineManager.AddLine(new Point(rBorder.Left, rBorder.Bottom), new Point(rBorder.Left, rBorder.Top), a_Label.BorderColor);
        }

        private void DrawMapCellOutline(Label a_Label)
        {
            Rectangle rOutline = m_GameMap.GetMapCellScreenBounds(a_Label.MapCoordinates);
            m_LineManager.AddLine(new Point(rOutline.Left, rOutline.Top), new Point(rOutline.Right, rOutline.Top), a_Label.BorderColor);
            m_LineManager.AddLine(new Point(rOutline.Right, rOutline.Top), new Point(rOutline.Right, rOutline.Bottom), a_Label.BorderColor);
            m_LineManager.AddLine(new Point(rOutline.Right, rOutline.Bottom), new Point(rOutline.Left, rOutline.Bottom), a_Label.BorderColor);
            m_LineManager.AddLine(new Point(rOutline.Left, rOutline.Bottom), new Point(rOutline.Left, rOutline.Top), a_Label.BorderColor);
        }

        protected Point GetMapCellTopRight(Label a_Label)
        {
            return m_GameMap.MapCellToScreenCoordinates(a_Label.MapCoordinates, CellSubLocation.TopRight);
        }


        private void BuildCellDefs()
        {
            // Get the array of pixel coordinates for each character and store it off
            m_aCellDefs = new Rectangle[256];
            int nTextureCellSizeX = (int)(m_Texture.Width / 16);
            int nTextureCellSizeY = (int)(m_Texture.Height / 16);

            
            m_pCellScreenSize = new Point(nTextureCellSizeX, nTextureCellSizeY);

            int nCount = 0;
            for (int i = 0; i < 16; i++)
            {
                for (int j = 0; j < 16; j++)
                {
                    m_aCellDefs[nCount] = new Rectangle(j * nTextureCellSizeX, i * nTextureCellSizeY, nTextureCellSizeX, nTextureCellSizeY);
                    nCount++;
                }
            }
        }

        public void Update(GameTime a_GameTime)
        {
            float dt = a_GameTime.ElapsedRealTime.Milliseconds / 1000.0f;

            foreach (KeyValuePair<int, Label> l in m_Labels)
            {
                if (l.Value.m_Entity != null)
                {
                    l.Value.MapCoordinates = l.Value.m_Entity.Position;
                }

                if (l.Value.m_UseTimeLeft)
                {
                    l.Value.m_TimeLeft -= dt;

                    if (l.Value.m_TimeLeft < 0)
                    {
                        m_Labels.Remove(l.Key);     // deferred removes? this means we remove one per frame...
                        break;
                    }
                }
            }
        }

        public void Draw(GraphicsDevice a_Device)
        {
            #region SpriteBatch 
            m_SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            m_SpriteBatch.GraphicsDevice.SamplerStates[0].MagFilter = TextureFilter.Point;

            foreach (KeyValuePair<int, Label> l in m_Labels)
            {                    
                char[] aText = l.Value.m_Text.ToCharArray();
                
                // where the label is drawn
                Point pStartLoc = m_GameMap.MapCellToScreenCoordinates(l.Value.MapCoordinates, CellSubLocation.TopRight);
                
                Point pLineStart = pStartLoc;
                Point PLineEnd = new Point(pLineStart.X + 8, pLineStart.Y - 8);
                
                int labelWidth = (aText.Length + 1) * 8;
                int rightEdge = TerrainWindow.Instance.WindowWidth;
                if (DetailWindow.EnemyWindow.IsActive)
                    rightEdge = DetailWindow.EnemyWindow.Position.X - 16;

                int leftEdge = 0;
                if (DetailWindow.HeroWindow.IsActive)
                    leftEdge = DetailWindow.HeroWindow.Position.X + DetailWindow.HeroWindow.WindowWidth + 16;

                pStartLoc.X = Math.Max(leftEdge, Math.Min(rightEdge - labelWidth, pStartLoc.X + 8));  // don't go offscreen
                pStartLoc.Y -= 24;      // move the label start location up (so it's above the target)

                Rectangle rectBackground = new Rectangle(pStartLoc.X, pStartLoc.Y, labelWidth, 16);

                // Draw the background
                if (l.Value.BackgroundColor.A > 0)
                {
                    m_SpriteBatch.Draw(m_Texture, rectBackground, GetRenderSource((char)255), l.Value.BackgroundColor);
                }

                for(int i = 0; i < aText.Length; i++)
                {
                   Rectangle rect = new Rectangle(pStartLoc.X + (8 * i), pStartLoc.Y + 1, 16, 16);
                   m_SpriteBatch.Draw(m_Texture, rect, GetRenderSource(aText[i]), l.Value.TextColor); 
                }

                Point borderPos = pStartLoc;    // this is dumb :(
                borderPos.X -= 8;
                borderPos.Y += 24;

                DrawBorder(l.Value, borderPos);
                DrawMapCellOutline(l.Value);
                m_LineManager.AddLine(pLineStart, PLineEnd, l.Value.BorderColor);
            }

            m_SpriteBatch.End();
            #endregion

            m_LineManager.Draw(a_Device);   // LineManager draws
        }

        public Rectangle GetRenderSource(char a_c)
        {
            return m_aCellDefs[(int)a_c];
        }

        private int Add(Label a_L)
        {
            int nLabel = m_nLabelID;
            m_nLabelID++;
            m_Labels.Add(nLabel, a_L);
            return nLabel;
        }

        public int Add(string a_sText, Point a_pMapCoord)
        {
            return Add(new Label(a_sText, a_pMapCoord));
        }

        public int Add(string a_sText, Entity a_Follow)
        {
            return Add(new Label(a_sText, a_Follow));
        }

        public int Add(string a_sText, Entity a_Follow, float a_TimeToShow)
        {
            return Add(new Label(a_sText, a_Follow, a_TimeToShow));
        }

        public void Add(string a_sText, Point a_pMapCoord, string a_sLabelName)
        {
            int nCRC = a_sLabelName.GetHashCode();
            nCRC = nCRC * -1;
            
            if (!m_Labels.ContainsKey(nCRC))
            {
                Label Label = new Label(a_sText, a_pMapCoord);
                m_Labels.Add(nCRC, Label);
            }
            else
            {
                Label Label = new Label(a_sText, a_pMapCoord);
                m_Labels[nCRC] = Label;
            }
        }

        public void RemoveAll()
        {
            m_Labels.Clear();
        }

        public void Remove(Entity a_Entity)
        {
            foreach (KeyValuePair<int, Label> l in m_Labels)
            {
                if (l.Value.m_Entity == a_Entity)
                {
                    m_Labels.Remove(l.Key);
                    break;
                }
            }
        }

        public bool Remove(int a_nID)
        {
            if (m_Labels.ContainsKey(a_nID))
            {
                m_Labels.Remove(a_nID);
                return true;
            }

            return false;
        }

        public bool Remove(string a_sLabelName)
        {
            int nCRC = a_sLabelName.GetHashCode();
            nCRC = nCRC * -1;

            if (m_Labels.ContainsKey(nCRC))
            {
                m_Labels.Remove(nCRC);
                return true;
            }

            return false;
        }

        public bool Move(int a_nID, Point a_pNewCoords)
        {
            if (m_Labels.ContainsKey(a_nID))
            {
                m_Labels[a_nID].MapCoordinates = a_pNewCoords;
                return true;
            }

            return false;
        }

        public bool Move(string a_sLabelName, Point a_pNewCoords)
        {
            int nCRC = a_sLabelName.GetHashCode();
            nCRC = nCRC * -1;

            if (m_Labels.ContainsKey(nCRC))
            {
                m_Labels[nCRC].MapCoordinates = a_pNewCoords;
                return true;
            }

            return false;
        }

        public bool SetText(int a_nID, string a_sNewText)
        {
            if (m_Labels.ContainsKey(a_nID))
            {
                m_Labels[a_nID].m_Text = a_sNewText;
                return true;
            }

            return false;
        }

        public bool SetText(string a_sLabelName, string a_sNewText)
        {
            int nCRC = a_sLabelName.GetHashCode();
            nCRC = nCRC * -1;

            if (m_Labels.ContainsKey(nCRC))
            {
                m_Labels[nCRC].m_Text = a_sNewText;
                return true;
            }

            return false;
        }

        #region Console Commands
        private void CONSOLE_AddLabel(string[] a_sArgs)
        {
            int nPosX = System.Convert.ToInt32(a_sArgs[0]);
            int nPosY = System.Convert.ToInt32(a_sArgs[1]);
            if (a_sArgs.Length > 3)
            {
                Add(a_sArgs[2], new Point(nPosX, nPosY), a_sArgs[3]);
            }
            else
            {
                Add(a_sArgs[2], new Point(nPosX, nPosY), a_sArgs[2]);
            }
        }

        private void CONSOLE_ClearLabels(string[] a_sArgs)
        {
            m_Labels.Clear();
        }

        public void CONSOLE_RemoveLabel(string[] a_sArgs)
        {
            if (!Remove(a_sArgs[0]))
            {
                ConsoleWindow.Instance.PrintError("LABEL DOES NOT EXIST: " + a_sArgs[0]);
            }
        }

        public void CONSOLE_MoveLabel(string[] a_sArgs)
        {
            int nPosX = System.Convert.ToInt32(a_sArgs[1]);
            int nPosY = System.Convert.ToInt32(a_sArgs[2]);
            if (!Move(a_sArgs[0], new Point(nPosX, nPosY)))
            {
                ConsoleWindow.Instance.PrintError("LABEL DOES NOT EXIST: " + a_sArgs[0]);
            }
        }

        #endregion

    }
}
